package com.victor.gateway.filter;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.victor.common.core.constant.HttpStatus;
import com.victor.common.core.constant.SecurityConstants;
import com.victor.common.core.exception.EncryptException;
import com.victor.common.core.utils.EncryptUtils;
import com.victor.common.core.utils.ServletUtils;
import com.victor.common.core.utils.StringUtils;
import com.victor.common.entity.system.SysThirdKey;
import com.victor.gateway.config.properties.DecryptProperties;
import com.victor.system.api.RemoteSysHospitalService;
import lombok.SneakyThrows;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;

@Component
public class ReqFilter implements GlobalFilter, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(ReqFilter.class);

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private DecryptProperties decryptProperties;

    @Autowired
    ServerCodecConfigurer codecConfigurer;


    @Lazy
    @Resource
    RemoteSysHospitalService remoteSysHospitalService;

    private static final String REQUEST_PREFIX = "\n--------------------------------- Request  Info -----------------------------";

    private static final String REQUEST_TAIL = "\n-----------------------------------------------------------------------------";

    private static final String RESPONSE_PREFIX = "\n--------------------------------- Response Info -----------------------------";

    private static final String RESPONSE_TAIL = "\n-------------------------------------------------------------------------->>>";

    private static final String logName = "gatewaylog:";

    ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,
                                        CachedBodyOutputMessage outputMessage, URI uri) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0L) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }

            @Override
            public URI getURI() {
                return uri;
            }
        };
    }


    private Mono<Void> returnMononew(GatewayFilterChain chain, ServerWebExchange exchange) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        }));
    }

    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
        //重新构造request，参考ModifyRequestBodyGatewayFilterFactory
        ServerRequest serverRequest = ServerRequest.create(exchange, codecConfigurer.getReaders());
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        AtomicReference<String> bodyStr = new AtomicReference<>("");
        //重点
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            //因为约定了终端传参的格式，所以只考虑json的情况，如果是表单传参，请自行增加
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)) {
                String newBody = "";
                try {
                    // 解密body
                    newBody = EncryptUtils.decrypt2Data(body, decryptProperties.getPrivateKey());
                    bodyStr.set(newBody);
                } catch (Exception e) {
                    logger.error("[网关参数解密异常]请求路径:{},请求参数:{},错误信息:{}", exchange.getRequest().getPath(), body, e.getMessage());
                    return Mono.error(new EncryptException("非法参数！"));
                }
                return Mono.just(newBody);
            }
            return Mono.empty();
        });
        URI uri = serverRequest.uri();
        String query = uri.getQuery();
        try {
            if (StrUtil.isNotEmpty(query)) {
                try {
                    if (StringUtils.startsWithIgnoreCase(query, "0=")) {
                        query = query.replaceFirst("0=", StringUtils.EMPTY);
                    }
                    String value = EncryptUtils.decrypt2Data(query, decryptProperties.getPrivateKey());
                    Map<String, Object> paramMap = JSONUtil.parse(value).toBean(Map.class);
                    MultiValueMap<String, String> newParamMap = new LinkedMultiValueMap<>();
                    for (String key : paramMap.keySet()) {

                        if (paramMap.get(key) instanceof Map) {
                            Map<String, Object> objectMap = (Map<String, Object>) paramMap.get(key);
                            for (String keyInfo : objectMap.keySet()) {
                               newParamMap.add(key+"["+keyInfo+"]", ObjectUtil.isEmpty(objectMap.get(keyInfo)) ? "" : StrUtil.toString(objectMap.get(keyInfo)));
                            }
                        }else if(paramMap.get(key) instanceof List){
                            List<Object> list  = (List<Object>) paramMap.get(key);
                            for(Object obj : list){
                                newParamMap.add(key, ObjectUtil.isEmpty(obj) ? "" : StrUtil.toString(obj));
                            }
                        } else {
                            newParamMap.add(key, ObjectUtil.isEmpty(paramMap.get(key)) ? "" : StrUtil.toString(paramMap.get(key)));
                        }
                    }
                    uri = UriComponentsBuilder.fromUri(uri).replaceQueryParams(newParamMap).build().toUri();
                } catch (Exception e) {
                    logger.error("[网关参数解密异常]请求路径:{},请求参数:{}", exchange.getRequest().getPath(), query, e.getMessage());
                    return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "参数解密失败！", HttpStatus.BAD_METHOD);
                }

            } else if (HttpMethod.DELETE.equals(exchange.getRequest().getMethod()) || HttpMethod.GET.equals(exchange.getRequest().getMethod())) {
                String url = uri.getPath().substring(0, uri.getPath().lastIndexOf("/") + 1);
                String param = uri.getPath().substring(uri.getPath().lastIndexOf("/") + 1);
                try {
                    param = EncryptUtils.decrypt2Data(param, decryptProperties.getPrivateKey());
                    Map<String, String> paramMap = JSONUtil.parse(param).toBean(Map.class);
                    Object value = null;
                    for (String key : paramMap.keySet()) {
                        value = paramMap.get(key);
                    }
                    uri = UriComponentsBuilder.fromUri(uri).replacePath(url + value).build().toUri();
                } catch (Exception e) {

                }
            }
        } catch (Exception e) {
            logger.error("[网关参数解密异常]请求路径:{},请求参数:{}", exchange.getRequest().getPath(), query, e.getMessage());
            return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "非法参数！", HttpStatus.BAD_METHOD);
        }
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove("Content-Length");
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        URI finalUri = uri;

        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage, finalUri);
            final StopWatch stopWatch = new StopWatch();
            stopWatch.start(logName + UUID.fastUUID());
            printReqInfo(exchange, finalUri.getQuery(), bodyStr.get(), stopWatch);
            ServerHttpResponseDecorator decoratedResponse = printResInfo(exchange, stopWatch);
            return returnMononew(chain, exchange.mutate().request(decorator).response(decoratedResponse).build());
        }));
    }

    public void printReqInfo(ServerWebExchange exchange, String query, String body, StopWatch stopWatch) {
        StringBuilder reqMsg = new StringBuilder();
        // 获取请求信息
        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethodValue();
        URI uri = request.getURI();
        // 拼接请求日志
        reqMsg.append(REQUEST_PREFIX);
        reqMsg.append("\n 追踪 日志名=").append(stopWatch.currentTaskName());
        reqMsg.append("\n query=").append(query);
        reqMsg.append("\n body=").append(JSONUtil.toJsonStr(body));
        reqMsg.append("\n method=").append(method);
        reqMsg.append("\n url=").append(uri.getPath());
        reqMsg.append("\n requestTime=").append(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
        reqMsg.append(REQUEST_TAIL);
        logger.info(reqMsg.toString()); // 打印入参日志
    }

    public ServerHttpResponseDecorator printResInfo(ServerWebExchange exchange, StopWatch stopWatch) {
        StringBuilder resMsg = new StringBuilder();
        resMsg.append(RESPONSE_PREFIX);
        ServerHttpResponse response = exchange.getResponse();
        DataBufferFactory bufferFactory = response.bufferFactory();
        return new ServerHttpResponseDecorator(response) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (response.isCommitted()) {
                    return Mono.empty();
                }
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        //dataBuffer合并成一个，解决获取结果不全问题
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        String responseResult = new String(content, Charset.forName("UTF-8"));

                        //resMsg.append("\n status=").append(this.getStatusCode());
                        //resMsg.append("\n header=").append(this.getHeaders());
                        resMsg.append("\n 追踪 日志名=").append(stopWatch.currentTaskName());
                        resMsg.append("\n responseResult=").append(responseResult);
                        resMsg.append(RESPONSE_TAIL);

                        // 计算请求时间
                        resMsg.append("耗时ms:").append(stopWatch.getTotalTimeMillis());
                        if (stopWatch.isRunning()) {
                            stopWatch.stop();
                        }
                        logger.info(resMsg.toString()); // 打印结果日志
                        return bufferFactory.wrap(content);
                    }));
                }
                return super.writeWith(body);
            }
        };
    }

    protected String parseRequestQuery(List<String> stringList) {
        if (!stringList.isEmpty()) {
            return StringUtils.join(stringList.toArray(), ",");
        }
        return "";
    }

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath(); // 当前调用方法的url
        HttpHeaders headers = request.getHeaders();

        // 登录处理第三方参数解密
        List<String> thirdList = Arrays.asList(decryptProperties.getThirdListStr().split(","));
        for (String value : thirdList) {
            if (antPathMatcher.match(value, path)) {
                return thirdReadBody(exchange, chain);
            }
        }

        // 是否开启参数解密
        if (!decryptProperties.isEnabled()) {
            return chain.filter(exchange);
        }
        //  登录跳过网关验证，检查白名单
        List<String> whiteList = Arrays.asList(decryptProperties.getWhiteListStr().split(","));
        for (String value : whiteList) {
            if (antPathMatcher.match(value, path)) {
                return chain.filter(exchange);
            }
        }


        // 处理参数--multipart上传文件不走解密 --后续其他不走的也可以加在这里
        MediaType contentType = headers.getContentType();
        if (ObjectUtil.isNotEmpty(contentType) &&
                StrUtil.isNotBlank(contentType.toString()) &&
                StringUtils.startsWithIgnoreCase(contentType.toString(), MediaType.MULTIPART_FORM_DATA_VALUE)) {
            return chain.filter(exchange);
        }
        return readBody(exchange, chain);
    }

    /**
     * 是否是Json请求
     *
     * @param exchange HTTP请求
     */
    public boolean isJsonRequest(ServerWebExchange exchange) {
        String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
        return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE) ||
                StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
    }

    /**
     * 第三方参数解密
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> thirdReadBody(ServerWebExchange exchange, GatewayFilterChain chain) {

        String thirdPrivateKey = "";

        String companyInfo = exchange.getRequest().getHeaders().getFirst("companyInfo");

        if (StrUtil.isBlank(companyInfo)) {
            companyInfo = "9E824E6A-830A-4210-946B-4A51357EDA0D";
        }

        SysThirdKey thirdkey = new SysThirdKey();
        thirdkey.setHeaderInfo(companyInfo);
        try {
            ExecutorService executor = Executors.newFixedThreadPool(1);
            CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> {
                return findThirdKey(thirdkey);
            }, executor);
            thirdPrivateKey = f.get();
            executor.shutdown();
        } catch (Exception e) {
            logger.error("", e);
            return Mono.error(new EncryptException("非法参数！"));
        }

        String finalThirdPrivateKey = thirdPrivateKey;

        //重新构造request，参考ModifyRequestBodyGatewayFilterFactory
        ServerRequest serverRequest = ServerRequest.create(exchange,codecConfigurer.getReaders());
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        AtomicReference<String> bodyStr = new AtomicReference<>("");
        //重点
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            //因为约定了终端传参的格式，所以只考虑json的情况，如果是表单传参，请自行增加
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)) {
                String newBody = "";
                try {
                    // 解密body
                    try {
                        newBody = EncryptUtils.decrypt2Data(body, finalThirdPrivateKey);
                    } catch (Exception e) {
                        newBody = EncryptUtils.decrypt2Data(body, finalThirdPrivateKey);
                    }
                    newBody = UnicodeUtil.toString(newBody);
                    bodyStr.set(newBody);
                } catch (Exception e) {
                    logger.error("", e);
                    logger.error("[网关参数解密异常]请求路径:{},请求参数:{},错误信息:{}", exchange.getRequest().getPath(), body, e);
                    return Mono.error(new EncryptException("非法参数！"));
                }
                return Mono.just(newBody);
            }
            return Mono.empty();
        });
        URI uri = serverRequest.uri();
        String query = uri.getQuery();
        try {
            if (StrUtil.isNotEmpty(query)) {
                try {
                    if (StringUtils.startsWithIgnoreCase(query, "0=")) {
                        query = query.replaceFirst("0=", StringUtils.EMPTY);
                    }
                    String value = null;
                    try {
                        value = EncryptUtils.decrypt2Data(query, thirdPrivateKey);
                    } catch (Exception e) {
                        value = EncryptUtils.decrypt2Data2(query, thirdPrivateKey);
                    }

                    value = UnicodeUtil.toString(value);
                    Map<String, Object> paramMap = JSONUtil.parse(value).toBean(Map.class);
                    MultiValueMap<String, String> newParamMap = new LinkedMultiValueMap<>();
                    for (String key : paramMap.keySet()) {
                        newParamMap.add(key, ObjectUtil.isEmpty(paramMap.get(key)) ? "" : StrUtil.toString(paramMap.get(key)));
                    }
                    uri = UriComponentsBuilder.fromUri(uri).replaceQueryParams(newParamMap).build().toUri();
                } catch (Exception e) {
                    logger.error("", e);
                    logger.error("[网关参数解密异常]请求路径:{},请求参数:{}", exchange.getRequest().getPath(), query, e.getMessage());
                    return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "参数解密失败！", HttpStatus.BAD_METHOD);
                }

            } else if (HttpMethod.DELETE.equals(exchange.getRequest().getMethod()) || HttpMethod.GET.equals(exchange.getRequest().getMethod())) {
                String url = uri.getPath().substring(0, uri.getPath().lastIndexOf("/") + 1);
                String param = uri.getPath().substring(uri.getPath().lastIndexOf("/") + 1);
                try {
                    param = EncryptUtils.decrypt2Data(param, thirdPrivateKey);
                    param = UnicodeUtil.toString(param);
                    Map<String, String> paramMap = JSONUtil.parse(param).toBean(Map.class);
                    Object value = null;
                    for (String key : paramMap.keySet()) {
                        value = paramMap.get(key);
                    }
                    uri = UriComponentsBuilder.fromUri(uri).replacePath(url + value).build().toUri();
                } catch (Exception e) {

                }
            }
        } catch (Exception e) {
            logger.error("[网关参数解密异常]请求路径:{},请求参数:{}", exchange.getRequest().getPath(), query, e.getMessage());
            return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "非法参数！", HttpStatus.BAD_METHOD);
        }
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove("Content-Length");
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        URI finalUri = uri;

        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage, finalUri);
            final StopWatch stopWatch = new StopWatch();
            stopWatch.start(logName + UUID.fastUUID());
            printReqInfo(exchange, finalUri.getQuery(), bodyStr.get(), stopWatch);
            ServerHttpResponseDecorator decoratedResponse = printResInfo(exchange, stopWatch);
            return returnMononew(chain, exchange.mutate().request(decorator).response(decoratedResponse).build());
        }));
    }

    /**
     * 获取第三方用户私钥
     *
     * @param key
     * @return
     */
    public String findThirdKey(SysThirdKey key) {
        String s = remoteSysHospitalService.findList(key, SecurityConstants.INNER).getData().get(0).getPrivateKey();
        return s;
    }


    @Override
    public int getOrder() {
        return -2;
    }
}